package sfeir.gwt.ergosum.server.service;

import static com.google.appengine.api.datastore.FetchOptions.Builder.withChunkSize;

import java.util.ArrayList;
import java.util.List;

import sfeir.gwt.ergosum.client.ClientApi.NotLoggedInException;
import sfeir.gwt.ergosum.client.model.Address;
import sfeir.gwt.ergosum.client.model.Email;
import sfeir.gwt.ergosum.client.model.GeoLocalisation;
import sfeir.gwt.ergosum.client.model.ImageURL;
import sfeir.gwt.ergosum.client.model.NetworkItem;
import sfeir.gwt.ergosum.client.model.Person;
import sfeir.gwt.ergosum.client.model.Tel;
import sfeir.gwt.ergosum.server.util.Pair;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.PreparedQuery.TooManyResultsException;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.Transform;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.inject.Singleton;
import com.google.appengine.api.users.User;

@Singleton
public class ProfileServiceImpl implements ProfileService {

    private static final UserService userService = UserServiceFactory.getUserService();
    private static final DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService();

    private Entity fetchProfileEntity(String account) {
        Query query = new Query("Profile");
        if (!account.contains("@"))
            query.addFilter("alias", FilterOperator.EQUAL, account);
        else
            query.addFilter("account", FilterOperator.EQUAL, account);
        PreparedQuery pq = datastoreService.prepare(query);
        Entity entity = null;
        try {
            entity = pq.asSingleEntity();
        } catch (TooManyResultsException e) {

            // entity = pq.
        }

        return entity;
    }

    private Person createPersonFromEntity(Entity entity) {
        Person p = null;

        if (null != entity) {
            p = new Person((String) entity.getProperty("firstname"), (String) entity.getProperty("name"));
            p.setAboutMe((String) entity.getProperty("about"));
            p.setAlias((String) entity.getProperty("alias"));
            p.setBirthDay((String) entity.getProperty("birthday"));
            p.setCompany((String) entity.getProperty("company"));
            p.setNickname((String) entity.getProperty("nickname"));
            p.setPhoto(new ImageURL((String) entity.getProperty("image")));
            Long timezone = (Long) entity.getProperty("timezone");
            if (null != timezone)
                p.setTimeZone(timezone.intValue());
            p.setTitle((String) entity.getProperty("title"));
            p.setUrl((String) entity.getProperty("url"));
            Double geolat = (Double) entity.getProperty("geolat");
            if (null != geolat)
                p.setGeo(new GeoLocalisation(geolat, (Double) entity.getProperty("geolon")));
            Query children = new Query("Address", entity.getKey());
            for (Entity child : datastoreService.prepare(children).asList(
                    withChunkSize(FetchOptions.DEFAULT_CHUNK_SIZE))) {
                p.getAddresses()
                        .add(
                                new Address((String) child.getProperty("value"), ((Long) child.getProperty("type"))
                                        .intValue()));
            }
            children = new Query("Email", entity.getKey());
            for (Entity child : datastoreService.prepare(children).asList(
                    withChunkSize(FetchOptions.DEFAULT_CHUNK_SIZE))) {
                p.getEmails().add(
                        new Email((String) child.getProperty("value"), ((Long) child.getProperty("type")).intValue()));
            }
            children = new Query("Network", entity.getKey());
            for (Entity child : datastoreService.prepare(children).asList(
                    withChunkSize(FetchOptions.DEFAULT_CHUNK_SIZE))) {
                p.getNetworkItems().add(
                        new NetworkItem((String) child.getProperty("type"), (String) child.getProperty("value")));
            }
            children = new Query("Tel", entity.getKey());
            for (Entity child : datastoreService.prepare(children).asList(
                    withChunkSize(FetchOptions.DEFAULT_CHUNK_SIZE))) {
                p.getTels().add(
                        new Tel((String) child.getProperty("value"), ((Long) child.getProperty("type")).intValue()));
            }
        }
        return p;
    }

    public Person getUserProfile() throws NotLoggedInException {
        User user = userService.getCurrentUser();
        Entity entity = fetchProfileEntity(user.getEmail());
        Person p = createPersonFromEntity(entity);
        if (p == null) {
            p = new Person();
            p.setAlias(user.getEmail());
            p.setNickname(user.getNickname());
            List<Email> list = new ArrayList<Email>();
            list.add(new Email(user.getEmail()));
            p.setEmails(list);
        }
        return p;
    }

    public Long savePerson(Person p) throws NotLoggedInException {
        deleteCurrentProfile();
        if (p.getAlias() == null || p.getAlias().isEmpty()) {
            p.setAlias(userService.getCurrentUser().getEmail());
        }
        Entity entity = saveProfileEntity(p, new Entity("Profile"));
        return entity.getKey().getId();
    }

    public void updatePerson(Person p) throws NotLoggedInException {
        // saveProfileEntity(p, fetchUserProfileEntity());
        deleteCurrentProfile();
        savePerson(p);
    }

    private Entity saveProfileEntity(Person p, Entity entity) {
        entity.setProperty("account", userService.getCurrentUser().getEmail());
        entity.setProperty("alias", p.getAlias());
        entity.setProperty("about", p.getAboutMe());
        entity.setProperty("birthday", p.getBirthDay());
        entity.setProperty("company", p.getCompany());
        entity.setProperty("firstname", p.getFirstname());
        if (null != p.getGeo()) {
            entity.setProperty("geolat", p.getGeo().getLatitude());
            entity.setProperty("geolon", p.getGeo().getLongitude());
        }
        entity.setProperty("name", p.getName());
        entity.setProperty("nickname", p.getNickname());
        entity.setProperty("image", p.getPhoto().getImageUrl());
        entity.setProperty("timezone", p.getTimeZone());
        entity.setProperty("title", p.getTitle());
        entity.setProperty("url", p.getUrl());
        Key key = datastoreService.put(entity);

        for (Address addr : p.getAddresses()) {
            Entity ent = new Entity("Address", key);
            ent.setProperty("value", addr.getFullValue());
            ent.setProperty("type", addr.getType());
            datastoreService.put(ent);
        }
        for (Email email : p.getEmails()) {
            Entity ent = new Entity("Email", key);
            ent.setProperty("value", email.getEmail());
            ent.setProperty("type", email.getTypeEmail());
            datastoreService.put(ent);
        }
        for (NetworkItem net : p.getNetworkItems()) {
            Entity ent = new Entity("Network", key);
            ent.setProperty("value", net.getNetworkItemLink());
            ent.setProperty("type", net.getNetworkItemType());
            datastoreService.put(ent);
        }
        for (Tel tel : p.getTels()) {
            Entity ent = new Entity("Tel", key);
            ent.setProperty("value", tel.getPhoneNumber());
            ent.setProperty("type", tel.getType());
            datastoreService.put(ent);
        }
        return entity;
    }

    public void deleteCurrentProfile() throws NotLoggedInException {
        // MemcacheServiceFactory.getMemcacheService().delete(userService.getCurrentUser());
        MemcacheServiceFactory.getMemcacheService().delete(userService.getCurrentUser().getEmail());
        Entity entity = fetchProfileEntity(userService.getCurrentUser().getEmail());
        if (null != entity)
            datastoreService.delete(entity.getKey());
    }

    @Override
    public Person getProfile(String email) {
        Entity entity = fetchProfileEntity(email);
        return createPersonFromEntity(entity);
    }

    @Override
    public String saveProfilePicture(String mime, byte[] data, String host) throws NotLoggedInException {
        Image image = ImagesServiceFactory.makeImage(data);
        Transform resize = ImagesServiceFactory.makeResize(80, 100);
        ImagesService imgService = ImagesServiceFactory.getImagesService();
        Image newImage = imgService.applyTransform(resize, image);

        Entity entity = fetchPictureEntity(userService.getCurrentUser().getEmail());
        if (null == entity)
            entity = new Entity("Picture");//fetchProfileEntity(userService.getCurrentUser().getEmail());
        entity.setProperty("pictureEmail", userService.getCurrentUser().getEmail());
        entity.setProperty("pictureMime", mime);
        entity.setProperty("pictureData", new Blob(newImage.getImageData()));
        String url = "http://" + host + "/picture/" + userService.getCurrentUser().getEmail() + ".png";
        entity.setProperty("image", url);
        datastoreService.put(entity);
//        MemcacheServiceFactory.getMemcacheService().delete(userService.getCurrentUser().getEmail());
        return url;
    }

    @Override
    public Pair<byte[], String> getProfilePicture(String email) {
        Entity entity = fetchPictureEntity(email);
        if (null == entity)
            return null;
        Blob bytes = (Blob) entity.getProperty("pictureData");
        String mime = (String) entity.getProperty("pictureMime");
        if (null == bytes)
            return null;
        return new Pair<byte[], String>(bytes.getBytes(), mime);
    }

    private Entity fetchPictureEntity(String email) {
        Entity entity = null;//fetchProfileEntity(email);
        Query q = new Query("Picture").addFilter("pictureEmail", FilterOperator.EQUAL, email);
        entity = datastoreService.prepare(q).asSingleEntity();
        return entity;
    }
}